#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

#include <cstring>
#include <iostream>
#include <thread>

#define PORT            8080
#define LINSTEN_BLOCK   20
#define BUFFER_LEN      4096
#define SET_NONBLOCK    0

bool setIoMode(int fd, int mode);
void routine(int clientfd);

int main(int argc, char**argv)
{
    // 1. Create socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
        std::cout << "socket return " << errno << ", " << strerror(errno) << std::endl;
        return -1;
    }

    // 2. Set the port and bind it.
    sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(sockaddr_in));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htons(INADDR_ANY); // bind ip address.
    serverAddr.sin_port = htons(PORT);  // bind port.
    if (bind(listenfd, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        std::cout << "bind return " << errno << ", " << strerror(errno) << std::endl;
        return -2;
    }

#if SET_NONBLOCK
    // set nonblock mode.
    setIoMode(listenfd, O_NONBLOCK);

#endif

    // 3. listening port.
    if (listen(listenfd, LINSTEN_BLOCK) == -1) {
        std::cout << "listen return " << errno << ", " << strerror(errno) << std::endl;
        return -3;
    }

    std::cout << "server listening port " << PORT << std::endl;
    while(1) {
        // 4. accept connect.
        sockaddr_in clientAddr;
        memset(&clientAddr, 0, sizeof(clientAddr));
        socklen_t clienLen = sizeof(clientAddr);
        int clientfd = accept(listenfd, (sockaddr *)&clientAddr, &clienLen);
        if (clientfd == -1) {
            std::cout << "accept return " << errno << ", " << strerror(errno) << std::endl;
            continue;
        }

        std::cout << "client fd " << clientfd << std::endl;
        std::thread thread(routine, clientfd);
        thread.detach();
    }
    close(listenfd);
    return 0;
}

bool setIoMode(int fd, int mode)
{
    int flag = fcntl(fd, F_GETFL, 0);
    if (flag == -1) {
        std::cout << "fcntl get flags return " << errno << ", " << strerror(errno) << std::endl;
        return false;
    }
    flag |= mode;
    if (fcntl(fd, F_SETFL, flag) == -1) {
        std::cout << "fcntl set flags return " << errno << ", " << strerror(errno) << std::endl;
        return false;
    }
    return true;
}

void routine(int clientfd)
{
    const char *msg = "Hello, Client!";
    while (1) {
        // 5. send message.
        if (send(clientfd, msg, strlen(msg), 0) == -1) {
            std::cout << "send buffer return " << errno << ", " << strerror(errno) << std::endl;
            continue;
        }

        // 6. recv message
        char buffer[BUFFER_LEN];
        int ret = recv(clientfd, buffer, BUFFER_LEN, 0);
        if (ret == 0) {
            std::cout << "client " << clientfd << " connection dropped" << std::endl;
            break;
        } else if (ret == -1) {
            std::cout << "recv buffer return " << errno << ", " << strerror(errno) << std::endl;
            break;
        }
        std::cout << "recv buffer from "<< clientfd << ": " << buffer << std::endl;
    }
    close(clientfd);
    std::cout << "End of client " << clientfd << std::endl;
}
